/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace Cobalt.Content.Pipeline.Graphics
{
	[ContentProcessor( DisplayName = "Cobalt Material Processor" )]
	public class CobaltMaterialProcessor : ContentProcessor<CobaltMaterialContent, CobaltMaterialContent>
	{
		public CobaltMaterialProcessor()
		{
			InitTexParams();
		}

		private OpaqueDataDictionary texParams = new OpaqueDataDictionary();

		[DefaultValue( typeof( Color ), "255, 0, 255, 255" )]
		public virtual Color ColorKeyColor
		{
			get { return texParams.GetValue( "ColorKeyColor", new Color( 0xFF, 0, 0xFF, 0xFF ) ); }
			set { texParams["ColorKeyColor"] = value; }
		}

		[DefaultValue( true )]
		public virtual bool ColorKeyEnabled
		{
			get { return texParams.GetValue( "ColorKeyEnabled", true ); }
			set { texParams["ColorKeyEnabled"] = value; }
		}
		[DefaultValue( true )]
		public virtual bool GenerateMipmaps
		{
			get { return texParams.GetValue( "GenerateMipmaps", true ); }
			set { texParams["GenerateMipmaps"] = value; }
		}

		[DefaultValue( false )]
		public virtual bool ResizeTexturesToPowerOfTwo
		{
			get { return texParams.GetValue( "ResizeToPowerOfTwo", false ); }
			set { texParams["ResizeToPowerOfTwo"] = value; }
		}

		[DefaultValue( TextureProcessorOutputFormat.DxtCompressed )]
		public virtual TextureProcessorOutputFormat TextureFormat
		{
			get { return texParams.GetValue( "TextureFormat", TextureProcessorOutputFormat.DxtCompressed ); }
			set { texParams["TextureFormat"] = value; }
		}

		private bool keepTags = true;
		[DefaultValue( true )]
		public virtual bool KeepTags { get { return keepTags; } set { keepTags = value; } }

		private bool keepPipelineTags = false;
		[DefaultValue( false )]
		public virtual bool KeepPipelineTags { get { return keepPipelineTags; } set { keepPipelineTags = value; } }

		private void InitTexParams()
		{
			ColorKeyColor = new Color( 0xFF, 0, 0xFF, 0xFF );
			ColorKeyEnabled = true;
			GenerateMipmaps = true;
			ResizeTexturesToPowerOfTwo = false;
			TextureFormat = TextureProcessorOutputFormat.DxtCompressed;
		}

		private ContentProcessorContext context;

		public override CobaltMaterialContent Process( CobaltMaterialContent input, ContentProcessorContext context )
		{
			this.context = context;

			CobaltMaterialContent ret = new CobaltMaterialContent();

			ret.Identity = input.Identity;
			ret.Name = input.Name;

			CopyMaterialProperties( input, ret );

			if( input.Parent != null )
			{
				CobaltMaterialContent parent = (CobaltMaterialContent)context.BuildAndLoadAsset<CobaltMaterialContent, MaterialContent>(
					input.Parent, "MaterialProcessor" );

				PushInheritedProperties( parent, ret );

				foreach( KeyValuePair<int, EffectMaterialContent> pair in parent.Layers )
				{
					EffectMaterialContent layer, inLayer;
					if( input.Layers.TryGetValue( pair.Key, out inLayer ) )
					{
						if( pair.Value == null )
							//parent suppressed its parent, but we don't want to propagate that
							continue;

						if( inLayer != null )
						{
							//we have redefined this layer, pass down any properties that weren't redefined

							layer = CloneLayer( inLayer );
							PushInheritedProperties( pair.Value, layer );

							if( layer.Effect == null )
								//our layer inherits the parent effect
								layer.Effect = pair.Value.Effect;
						}
						else
						{
							//we have defined this layer as a suppression
							layer = null;
						}
					}
					else
					{
						//we don't have this layer yet, clone it for ourselves
						layer = CloneLayer( pair.Value );
					}

					if( layer != null )
						//if this isn't a suppression, process it
						ProcessLayer( layer );

					ret.Layers[pair.Key] = layer;
				}
			}

			//include any layers we defined which the parent did not include
			foreach( KeyValuePair<int, EffectMaterialContent> pair in input.Layers )
			{
				if( ret.Layers.ContainsKey( pair.Key ) )
					//layer added while handling parent values
					continue;

				EffectMaterialContent layer = CloneLayer( pair.Value );

				ProcessLayer( layer );

				ret.Layers[pair.Key] = layer;
			}

			ProcessLayer( ret );

			if( keepTags )
			{
				//move tags
				foreach( KeyValuePair<string, object> pair in input.Tags )
				{
					object value = pair.Value;
					if( value is ExternalReference<TextureContent> )
						value = context.BuildAsset<TextureContent, TextureContent>( (ExternalReference<TextureContent>)value, "TextureProcessor", texParams, null, null );

					ret.Tags.Add( pair.Key, value );
				}
			}

			if( keepPipelineTags )
			{
				foreach( KeyValuePair<string, object> pair in input.PipelineTags )
				{
					object value = pair.Value;
					if( value is ExternalReference<TextureContent> )
						value = context.BuildAsset<TextureContent, TextureContent>( (ExternalReference<TextureContent>)value, "TextureProcessor", texParams, null, null );

					ret.Tags.Add( pair.Key, value );
				}
			}

			//pipeline tags are ignored by the writer, but we keep them in case any other
			//processor is using them to store data when manually invoking material builds
			foreach( KeyValuePair<string, object> pair in input.PipelineTags )
				ret.PipelineTags.Add( pair.Key, pair.Value );

			this.context = null;

			return ret;
		}

		private void ProcessLayer( MaterialContent layer )
		{
			List<KeyValuePair<string, ExternalReference<TextureContent>>> source =
				new List<KeyValuePair<string, ExternalReference<TextureContent>>>( layer.Textures );

			layer.Textures.Clear();

			foreach( KeyValuePair<string, ExternalReference<TextureContent>> pair in source )
			{
				layer.Textures[pair.Key] = context.BuildAsset<TextureContent, TextureContent>(
					pair.Value, "TextureProcessor", texParams, null, null );
			}

			EffectMaterialContent effectLayer = layer as EffectMaterialContent;
			if( effectLayer != null )
			{
				if( effectLayer.CompiledEffect == null )
				{
					if( effectLayer.Effect == null )
						throw new InvalidContentException( "Layer has no effect." );

					effectLayer.CompiledEffect = context.BuildAsset<EffectContent, CompiledEffect>(
						effectLayer.Effect, "EffectProcessor" );
				}
			}
		}

		private void PushInheritedProperties( MaterialContent parent, MaterialContent child )
		{
			foreach( KeyValuePair<string, object> pair in parent.OpaqueData )
			{
				if( pair.Key == "Effect" || pair.Key == "CompiledEffect" )
					continue;

				if( !child.OpaqueData.ContainsKey( pair.Key ) )
					child.OpaqueData[pair.Key] = pair.Value;
			}

			foreach( KeyValuePair<string, ExternalReference<TextureContent>> pair in parent.Textures )
			{
				if( !child.Textures.ContainsKey( pair.Key ) )
					child.Textures[pair.Key] = pair.Value;
			}
		}

		private void CopyMaterialProperties( MaterialContent source, MaterialContent dest )
		{
			foreach( KeyValuePair<string, object> pair in source.OpaqueData )
			{
				if( pair.Key == "Effect" || pair.Key == "CompiledEffect" )
					continue;

				dest.OpaqueData[pair.Key] = pair.Value;
			}

			foreach( KeyValuePair<string, ExternalReference<TextureContent>> pair in source.Textures )
			{
				dest.Textures[pair.Key] = pair.Value;
			}
		}

		private EffectMaterialContent CloneLayer( EffectMaterialContent layer )
		{
			EffectMaterialContent ret = new EffectMaterialContent();

			ret.Identity = layer.Identity;
			ret.Name = layer.Name;

			ret.Effect = layer.Effect;
			ret.CompiledEffect = layer.CompiledEffect;

			CopyMaterialProperties( layer, ret );

			return ret;
		}
	}
}